package org.zjt.config.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.zjt.config.annotation.SPI;


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Pattern;

/**
 * @author <a href="juntao.zhang@bkjk.com">juntao.zhang</a>
 * @Description: spi扩展加载类
 * @Package org.zjt.spiextensiondemo.util.SPIExtensionLoader
 * @date 2017/12/4 18:09
 * @see
 */
public class SPIExtensionLoader<T> {

    private static final String SERVICES_DIRECTORY = "META-INF/services/";

    private static final Logger logger = LoggerFactory.getLogger(SPIExtensionLoader.class);

    private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");

    private static final ConcurrentMap<Class<?>, SPIExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, SPIExtensionLoader<?>>();

    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();

    private final Class<?> type;

    private final Map<String,Class<?>> extensionMap;

    private final Integer supportedExtensions;

    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();




    private SPIExtensionLoader(Class<?> type) {
        if (!withExtensionAnnotation(type))
            throw new IllegalArgumentException("非spi拓展接口");
        this.type = type;
        extensionMap = loadExtensionMap();
        supportedExtensions = extensionMap.size();
    }

    private Map<String,Class<?>> loadExtensionMap() {
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        loadFile(extensionClasses,SERVICES_DIRECTORY);
        return extensionClasses;
    }

    @SuppressWarnings("unchecked")
    public T getAdaptiveExtension()  {
        try {
            SPI annotation = type.getAnnotation(SPI.class);
            if (Objects.nonNull(annotation)){
                String value = annotation.value();
                Class<?> aClass = extensionMap.get(value);
                if (Objects.isNull(aClass))
                    throw new NullPointerException(type.getName()+"无spi拓展类");
                if (!EXTENSION_INSTANCES.containsKey(aClass)) {
                    EXTENSION_INSTANCES.putIfAbsent(aClass,aClass.newInstance());
                }
                return (T) EXTENSION_INSTANCES.get(aClass);
            }
        }catch (InstantiationException | IllegalAccessException e){
            throw new RuntimeException(e);
        }
        throw new NullPointerException(type.getName()+"无spi拓展类");
    }


    @SuppressWarnings("unchecked")
    public T getExtension(String extensionName)  {
        try {
            if (StringUtils.hasText(extensionName)){
                Class<?> aClass = extensionMap.get(extensionName);
                if (Objects.isNull(aClass))
                    throw new NullPointerException(type.getName()+"无spi拓展类");
                if (!EXTENSION_INSTANCES.containsKey(aClass))
                    EXTENSION_INSTANCES.putIfAbsent(aClass,aClass.newInstance());
                return (T) EXTENSION_INSTANCES.get(aClass);
            }
        }catch (InstantiationException | IllegalAccessException e){
            throw new RuntimeException(e);
        }
        throw new NullPointerException(type.getName()+"无spi拓展类");
    }


    private static <T> boolean withExtensionAnnotation(Class<T> type) {
        return type.isAnnotationPresent(SPI.class);
    }

    private static ClassLoader findClassLoader() {
        return SPIExtensionLoader.class.getClassLoader();
    }

    private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
        String fileName = dir + type.getName();
        try {
            Enumeration<URL> urls;
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    try {
                        BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
                        try {
                            String line = null;
                            while ((line = reader.readLine()) != null) {
                                final int ci = line.indexOf('#');
                                if (ci >= 0) line = line.substring(0, ci);
                                line = line.trim();
                                if (line.length() > 0) {
                                    try {
                                        String name = null;
                                        int i = line.indexOf('=');
                                        if (i > 0) {
                                            name = line.substring(0, i).trim();
                                            line = line.substring(i + 1).trim();
                                        }
                                        if (line.length() > 0) {
                                            Class<?> clazz = Class.forName(line, true, classLoader);
                                            if (!type.isAssignableFrom(clazz)) {
                                                throw new IllegalStateException("Error when load extension class(interface: " +
                                                        type + ", class line: " + clazz.getName() + "), class "
                                                        + clazz.getName() + "is not subtype of interface.");
                                            }
                                            cachedNames.putIfAbsent(clazz,name);
                                            extensionClasses.put(name,clazz);
                                        }
                                    } catch (Throwable t) {
                                        IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
                                    }
                                }
                            } // end of while read lines
                        } finally {
                            reader.close();
                        }
                    } catch (Throwable t) {
                        logger.error("Exception when load extension class(interface: " +
                                type + ", class file: " + url + ") in " + url, t);
                    }
                } // end of while urls
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

    public Integer getSupportedExtensions() {
        return supportedExtensions;
    }

    @Override
    public String toString() {
        return this.getClass().getName() + "[" + type.getName() + "]";
    }

    public static <T> SPIExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (!EXTENSION_LOADERS.containsKey(type)){
            EXTENSION_LOADERS.putIfAbsent(type,new SPIExtensionLoader<T>(type));
        }
        return (SPIExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
}